//
// Created by benxb on 2021/11/4.
//

#include <gtest/gtest.h>
#include <fstream>
#include "compress/compressor.h"
#include "common/logger.h"
#include <cstdio>
#include <cstdlib>
#include <chrono>

using namespace benxdb;

const std::string data_file = "/home/ubuntu/benxdb/test/compress/lidar_data_0.txt";

// Test simple compress function
TEST(CompressorTest, TestSimpleCompress) {
    benxdb::Compressor compressor;
    std::vector<int64_t> x_column = {29595, 29769, 29941, 30118, 30297, 30489, 30703, 30929, 31177};
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(int64_t));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(int64_t)), &x_column[i], sizeof(int64_t));
    }
    std::string out;
    compressor.DeltaEncodeBigInt(const_cast<char *>(in_data.data()), in_data.size(), out);
    ASSERT_GT(out.size(), sizeof(uint8_t) + sizeof(int64_t) * 2);
    uint8_t delta_bit;
    uint64_t in_count;
    int64_t min_val;
    char *out_data = const_cast<char *>(out.data());
    memcpy(&delta_bit, out_data, sizeof(delta_bit));
    out_data += sizeof(delta_bit);
    memcpy(&in_count, out_data, sizeof(in_count));
    out_data += sizeof(in_count);
    memcpy(&min_val, out_data, sizeof(min_val));
    out_data += sizeof(min_val);
    EXPECT_EQ(delta_bit, 12);
    EXPECT_EQ(in_count, 9);
    EXPECT_EQ(min_val, 29595);
    int64_t expect_total_bit = sizeof(uint8_t) + sizeof(int64_t) * 2 + delta_bit * in_count/64*8;
    if (delta_bit*in_count % 64) {
        expect_total_bit += 8;
    }
    EXPECT_EQ(out.size(), expect_total_bit);
    LOG_DEBUG("deleta_bit: %d; in_count: %lu, min_val: %ld", delta_bit, in_count, min_val);
    printf("%lX\n", *reinterpret_cast<uint64_t*>(out_data));
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}

TEST(CompressorTest, TestDecimalSimpleCompress) {
    benxdb::Compressor compressor;
    std::vector<double> x_column = {29.595, 29.769, 29.941, 30.118, 30.297, 30.489, 30.703, 30.929, 31.177};
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(int64_t));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(int64_t)), &x_column[i], sizeof(int64_t));
    }
    std::string out;
    compressor.DeltaEncodeDecimal(const_cast<char *>(in_data.data()), in_data.size(), 3, out);
    ASSERT_GT(out.size(), sizeof(uint8_t) + sizeof(int64_t) * 2);
    uint8_t precision;
    uint8_t delta_bit;
    uint64_t in_count;
    int64_t min_val;
    char *out_data = const_cast<char *>(out.data());
    memcpy(&precision, out_data, sizeof(precision));
    out_data += sizeof(precision);
    memcpy(&delta_bit, out_data, sizeof(delta_bit));
    out_data += sizeof(delta_bit);
    memcpy(&in_count, out_data, sizeof(in_count));
    out_data += sizeof(in_count);
    memcpy(&min_val, out_data, sizeof(min_val));
    out_data += sizeof(min_val);
    EXPECT_EQ(precision, 3);
    EXPECT_EQ(delta_bit, 12);
    EXPECT_EQ(in_count, 9);
    EXPECT_EQ(min_val, 29595);
    int64_t expect_total_bit = sizeof(uint8_t) + sizeof(uint8_t) + sizeof(int64_t) * 2 + delta_bit * in_count/64*8;
    if (delta_bit*in_count % 64) {
        expect_total_bit += 8;
    }
    EXPECT_EQ(out.size(), expect_total_bit);
    LOG_DEBUG("deleta_bit: %d; in_count: %lu, min_val: %ld", delta_bit, in_count, min_val);
    printf("%lX\n", *reinterpret_cast<uint64_t*>(out_data));
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}

// Test exactly 8 bit delta compress
TEST(CompressorTest, Test8bitCompress) {
    benxdb::Compressor compressor;
    std::vector<int64_t> x_column = {1, 255, 511, 765, 1273, 1527, 1781, 2035, 2289};
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(int64_t));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(int64_t)), &x_column[i], sizeof(int64_t));
    }
    std::string out;
    compressor.DeltaEncodeBigInt(const_cast<char *>(in_data.data()), in_data.size(), out);
    std::string decodeStr;
    compressor.DeltaDecodeBigInt(const_cast<char *>(out.data()), out.size(), decodeStr);
    EXPECT_EQ(decodeStr.size()/sizeof(int64_t), x_column.size());
    for (int i = 0; i < x_column.size(); i++) {
        int64_t data;
        memcpy(&data, decodeStr.data() + i * sizeof(int64_t), sizeof(int64_t));
        EXPECT_EQ(data, x_column[i]);
    }
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}

// Test real small data_ for compress and decompress
TEST(CompressorTest, TestCompressDeCompress) {
    benxdb::Compressor compressor;
    std::vector<int64_t> x_column = {29595, 29769, 29941, 30118, 30297, 30489, 30703, 30929, 31177};
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(int64_t));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(int64_t)), &x_column[i], sizeof(int64_t));
    }
    std::string out;
    compressor.DeltaEncodeBigInt(const_cast<char *>(in_data.data()), in_data.size(), out);
    std::string decodeStr;
    compressor.DeltaDecodeBigInt(const_cast<char *>(out.data()), out.size(), decodeStr);
    EXPECT_EQ(decodeStr.size()/sizeof(int64_t), x_column.size());
    for (int i = 0; i < x_column.size(); i++) {
        int64_t data;
        memcpy(&data, decodeStr.data() + i * sizeof(int64_t), sizeof(int64_t));
        EXPECT_EQ(data, x_column[i]);
    }
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}

// Test real small data_ for compress and decompress for decimal
TEST(CompressorTest, TestDecimalCompressDeCompress) {
    benxdb::Compressor compressor;
    std::vector<double> x_column = {29.595, 29.769, 29.941, 30.118, 30.297, 30.489, 30.703, 30.929, 31.177};
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(int64_t));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(int64_t)), &x_column[i], sizeof(int64_t));
    }
    std::string out;
    compressor.DeltaEncodeDecimal(const_cast<char *>(in_data.data()), in_data.size(), 3, out);
    std::string decodeStr;
    compressor.DeltaDecodeDecimal(const_cast<char *>(out.data()), out.size(), decodeStr);
    EXPECT_EQ(decodeStr.size()/sizeof(int64_t), x_column.size());
    for (int i = 0; i < x_column.size(); i++) {
        double data;
        memcpy(&data, decodeStr.data() + i * sizeof(int64_t), sizeof(int64_t));
        EXPECT_EQ(data, x_column[i]);
    }
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}

// Test data_ from file
TEST(CompressorTest, TestFileCompressBigInt) {
    benxdb::Compressor compressor;
    std::ifstream data_io(data_file, std::ios::binary | std::ios::in);
    if (!data_io) {
        LOG_ERROR("file read error");
        return;
    }
    char line[100];
    double x, y, z, intensity;
    std::vector<int64_t> x_column;
    while (data_io.getline(line, 100)) {
        sscanf(line, "%lf %lf %lf %lf", &x, &y, &z, &intensity);
        x_column.push_back(static_cast<int64_t>(x*1000));
    }
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(int64_t));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(int64_t)), &x_column[i], sizeof(int64_t));
    }
    std::string out;
    compressor.DeltaEncodeBigInt(const_cast<char *>(in_data.data()), in_data.size(), out);
    std::string decodeStr;
    compressor.DeltaDecodeBigInt(const_cast<char *>(out.data()), out.size(), decodeStr);
    EXPECT_EQ(decodeStr.size()/sizeof(int64_t), x_column.size());
    for (int i = 0; i < x_column.size(); i++) {
        int64_t data;
        memcpy(&data, decodeStr.data() + i * sizeof(int64_t), sizeof(int64_t));
        EXPECT_EQ(data, x_column[i]);
    }
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}


TEST(CompressorTest, TestFileCompressDecimal) {
    benxdb::Compressor compressor;
    std::ifstream data_io(data_file, std::ios::binary | std::ios::in);
    if (!data_io) {
        LOG_ERROR("file read error");
        return;
    }
    char line[100];
    double x, y, z, intensity;
    std::vector<double> x_column;
    while (data_io.getline(line, 100)) {
        sscanf(line, "%lf %lf %lf %lf", &x, &y, &z, &intensity);
        x_column.push_back(x);
        if (x_column.size() > 64000) {
            break;
        }
    }
    std::string in_data;
    in_data.resize(x_column.size()*sizeof(double));
    for (int i = 0; i < x_column.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(double)), &x_column[i], sizeof(double));
    }

    auto start = std::chrono::steady_clock::now();
    std::string out;
    compressor.DeltaEncodeDecimal(const_cast<char *>(in_data.data()), in_data.size(), 6, out);
    std::string decodeStr;
    compressor.DeltaDecodeDecimal(const_cast<char *>(out.data()), out.size(), decodeStr);
    EXPECT_EQ(decodeStr.size()/sizeof(double), x_column.size());
    for (int i = 0; i < x_column.size(); i++) {
        double data;
        memcpy(&data, decodeStr.data() + i * sizeof(double), sizeof(double));
        EXPECT_EQ(data, x_column[i]);
    }
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
    std::cout << "Compress End, spend " << time_span.count() << std::endl;
}

TEST(CompressorTest, TestFileCompressDecimalRowType) {
    benxdb::Compressor compressor;
    std::ifstream data_io(data_file, std::ios::binary | std::ios::in);
    if (!data_io) {
        LOG_ERROR("file read error");
        return;
    }
    char line[100];
    double x, y, z, intensity;
    std::vector<double> table;
    while (data_io.getline(line, 100)) {
        sscanf(line, "%lf %lf %lf %lf", &x, &y, &z, &intensity);
        table.push_back(x);
        table.push_back(y);
        table.push_back(z);
    }
    std::string in_data;
    in_data.resize(table.size()*sizeof(double));
    for (int i = 0; i < table.size(); i++) {
        memcpy((void *) (in_data.data() + i * sizeof(double)), &table[i], sizeof(double));
    }
    std::string out;
    compressor.DeltaEncodeDecimal(const_cast<char *>(in_data.data()), in_data.size(), 3, out);
    std::string decodeStr;
    compressor.DeltaDecodeDecimal(const_cast<char *>(out.data()), out.size(), decodeStr);
    EXPECT_EQ(decodeStr.size()/sizeof(double), table.size());
    for (int i = 0; i < table.size(); i++) {
        double data;
        memcpy(&data, decodeStr.data() + i * sizeof(double), sizeof(double));
        EXPECT_EQ(data, table[i]);
    }
    LOG_DEBUG("before: %lu, after: %lu", in_data.size(), out.size());
}